package com.joshua.spring.util;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import okhttp3.*;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.*;
import java.net.URLEncoder;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * com.joshua.spring.common.util -- OkHttp3Utils
 * description:
 *
 * @author <a href="mailto:joshualwork@163.com">joshua_liu</a>
 * @date 2020/1/2 19:20
 */
@Slf4j
public class OkHttp3Utils {
    public static final MediaType JSON_MEDIA_TYPE = MediaType.parse("application/json;charset=utf-8");

    public static final MediaType XML_MEDIA_TYPE = MediaType.parse("text/xml");

    private volatile static OkHttpClient okHttpClientNoSsl = null;

    private volatile static OkHttpClient okHttpClientSsl = null;

    private volatile static OkHttpClient okHttpClient = null;

    private static final Long CONNECT_TIMEOUT = 5L;

    private static final Long READ_TIMEOUT = 15L;

    private OkHttp3Utils() {
    }

    private static OkHttpClient getInstance() {
        if (okHttpClientNoSsl == null) {
            synchronized (OkHttp3Utils.class) {
                if (okHttpClientNoSsl == null) {
                    okHttpClientNoSsl = new OkHttpClient.Builder()
                            .connectTimeout(CONNECT_TIMEOUT, TimeUnit.SECONDS)
                            .readTimeout(READ_TIMEOUT, TimeUnit.SECONDS)
                            .build();
                }
            }
        }
        okHttpClient = okHttpClientNoSsl;
        return okHttpClient;
    }

    private static OkHttpClient getInstance(String jksFilePath, String password) {
        if (okHttpClientSsl == null) {
            synchronized (OkHttp3Utils.class) {
                if (okHttpClientSsl == null) {
                    try {
                        SSLContext sslContext = SSLContext.getInstance("TLS");
                        TrustManager[] trustManagerFactory = {new X509TrustManager() {
                            @Override
                            public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
                            }

                            @Override
                            public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
                            }

                            @Override
                            public X509Certificate[] getAcceptedIssuers() {
                                return new X509Certificate[0];
                            }
                        }};
                        KeyStore clientKeyStore = KeyStore.getInstance("JKS");
                        FileInputStream fis1 = new FileInputStream(jksFilePath);
                        clientKeyStore.load(fis1, password.toCharArray());
                        KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
                        keyManagerFactory.init(clientKeyStore, password.toCharArray());
                        sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory, new SecureRandom());
                        okHttpClientSsl = new OkHttpClient.Builder()
                                .connectTimeout(CONNECT_TIMEOUT, TimeUnit.SECONDS)
                                .readTimeout(READ_TIMEOUT, TimeUnit.SECONDS)
                                .sslSocketFactory(sslContext.getSocketFactory(), (X509TrustManager) trustManagerFactory[0])
                                .build();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }
        okHttpClient = okHttpClientSsl;
        return okHttpClient;
    }

    public static String httpGet(String url, Map<String, String> headers, HashMap<String, String> params) throws IOException {
        return doGet(url, params, headers);
    }

    public static String httpGet(String url, HashMap<String, String> params) throws IOException {
        return httpGet(url, null, params);
    }

    public static <T> T httpGet(String url, Map<String, String> headers, HashMap<String, String> params, Class<T> clazz) throws IOException {
        String bodyString = doGet(url, params, headers);
        if (bodyString != null) {
            return JacksonUtils.map2pojo(bodyStringFormat(bodyString), clazz);
        }
        return null;
    }

    public static <T> T httpGet(String url, HashMap<String, String> params, Class<T> clazz) throws IOException {
        return httpGet(url, null, params, clazz);
    }

    public static <T> T httpGet(String url, Map<String, String> headers, Object requestObject, Class<T> clazz) throws IOException {
        StringBuilder paramsValue = new StringBuilder();
        String finalUrl = url;
        Map<String, Object> params = JacksonUtils.json2map(JacksonUtils.obj2json(requestObject));
        if (params != null && params.size() > 0) {
            for (Map.Entry<String, Object> entry : params.entrySet()) {
                if (StringUtils.isEmpty(entry.getKey()) || Objects.isNull(entry.getValue())) {
                    continue;
                }
                paramsValue = paramsValue.append(String.format("&%s=%s",
                        entry.getKey(), URLEncoder.encode(entry.getValue().toString(), "utf-8")));
            }
            finalUrl = String.format("%s?%s", url, paramsValue.substring(1));
        }
        String bodyString = doGet(finalUrl, headers);
        if (bodyString != null) {
            return JacksonUtils.map2pojo(bodyStringFormat(bodyString), clazz);
        }
        return null;
    }

    public static <T> T httpGet(String url, Object requestObject, Class<T> clazz) throws IOException {
        return httpGet(url, null, requestObject, clazz);
    }

    public static <T> T httpPost(String url, MediaEnum mediaEnum, Object data, Class<T> clazz) throws IOException {
        String bodyString = doPost(url, null, mediaEnum, data);
        T result = JacksonUtils.map2pojo(bodyStringFormat(bodyString), clazz);
        return result;
    }

    public static <T> T httpPost(String url, MediaEnum mediaEnum, HashMap<String, String> headers, Object data, Class<T> clazz) throws IOException {
        String bodyString = doPost(url, headers, mediaEnum, data);
        return JacksonUtils.map2pojo(bodyStringFormat(bodyString), clazz);
    }

    public static String httpPost(String url, MediaEnum mediaEnum, Object data) throws IOException {
        return doPost(url, null, mediaEnum, data);
    }

    public static String httpPost(String url, HashMap<String, String> headers, MediaEnum mediaEnum, Object data) throws IOException {
        return doPost(url, headers, mediaEnum, data);
    }

    public static <T> T httpPostForm(String url, HashMap<String, String> data, Class<T> clazz) throws IOException {
        OkHttpClient httpClient = getInstance();
        Request request;
        log.info("httpPost >>> {} >>> {}", url, data == null ? "" : data.toString());
        FormBody.Builder builder = new FormBody.Builder();
        for (Map.Entry<String, String> entry : data.entrySet()) {
            builder.add(entry.getKey(), entry.getValue());
        }
        request = new Request.Builder().url(url).post(builder.build()).build();
        Response response = httpClient.newCall(request).execute();
        if (response == null || response.body() == null) {
            log.info("httpPost >>> {} >>> {} >>> no response", url, data == null ? "" : JacksonUtils.mapToJson(data));
            return null;
        } else {
            String bodyString = response.body().string();
            log.info("httpPost >>> {} >>> {} >>> {}", url, data == null ? "" : JacksonUtils.mapToJson(data), bodyString);
            Map<String, Object> jsonObject = JacksonUtils.json2map(bodyString);
            jsonObject.put("response_json", bodyString);
            return JacksonUtils.map2pojo(jsonObject, clazz);
        }
    }

    public static boolean downloadFile(String url, String saveFullPath) throws IOException {
        File file = new File(saveFullPath);
        File folder = new File(file.getAbsolutePath().replace(file.getName(), ""));

        if (!folder.exists()) {
            folder.mkdirs();
        }
        OkHttpClient httpClient = getInstance();
        Request request = new Request.Builder().url(url).build();
        Response response = httpClient.newCall(request).execute();
        InputStream is;
        is = response.body().byteStream();
        FileOutputStream fos = new FileOutputStream(saveFullPath);
        int len;
        byte[] bytes = new byte[4096];
        while ((len = is.read(bytes)) != -1) {
            fos.write(bytes, 0, len);
        }
        fos.flush();
        is.close();
        fos.close();
        return true;
    }

    private static String doPost(String jksFilePath, String password, String url, HashMap<String, String> headers, MediaEnum mediaEnum, Object data) throws IOException {
        OkHttpClient httpClient = getInstance(jksFilePath, password);
        return doPost(httpClient, url, headers, mediaEnum, data);
    }

    private static String doPost(String url, HashMap<String, String> headers, MediaEnum mediaEnum, Object data) throws IOException {
        OkHttpClient httpClient = getInstance();
        return doPost(httpClient, url, headers, mediaEnum, data);
    }

    private static String doPost(OkHttpClient httpClient, String url, HashMap<String, String> headers, MediaEnum mediaEnum, Object data) throws IOException {
        Request request;
        RequestBody requestBody;
        Request.Builder requestBuilder;
        log.info("httpPost >>> {} >>> {}", url, data == null ? "" : JacksonUtils.obj2json(data));
        switch (mediaEnum) {
            case XML:
                requestBody = RequestBody.create(XML_MEDIA_TYPE, data.toString());
                requestBuilder = new Request.Builder().
                        addHeader("Content-Type", MediaEnum.XML.getContentType()).
                        url(url).post(requestBody);
                break;
            case FORM:
                Map<String, Object> params = JacksonUtils.obj2pojo(data, HashMap.class);
                FormBody.Builder builder = new FormBody.Builder();
                for (Map.Entry<String, Object> entry : params.entrySet()) {
                    if (!ObjectUtils.isEmpty(entry.getValue())) {
                        builder.add(entry.getKey(), entry.getValue().toString());
                    }
                }
                requestBuilder = new Request.Builder().url(url).post(builder.build());
                break;
            case JSON:
            default:
                requestBody = RequestBody.create(JSON_MEDIA_TYPE, JacksonUtils.obj2json(data));
                requestBuilder = new Request.Builder().
                        addHeader("Content-Type", MediaEnum.JSON.getContentType()).
                        url(url).post(requestBody);
                break;
        }
        if (headers != null) {
            for (Map.Entry<String, String> entry : headers.entrySet()) {
                requestBuilder.addHeader(entry.getKey(), entry.getValue());
            }
        }
        request = requestBuilder.build();
        Response response = httpClient.newCall(request).execute();
        if (response == null || response.body() == null) {
            log.info("httpPost >>> {} >>> {} >>> no response", url, data == null ? "" : JacksonUtils.obj2json(data));
            return null;
        } else {
            String bodyStr = response.body().string();
            log.info("httpPost >>> {} >>> {} >>> {}", url, data == null ? "" : JacksonUtils.obj2json(data), bodyStr);
            return bodyStr;
        }
    }

    private static String doGet(String url, HashMap<String, String> params, Map<String, String> headers) throws IOException {
        StringBuilder paramsValue = new StringBuilder();
        String finalUrl = url;
        if (params != null && params.size() > 0) {
            for (Map.Entry<String, String> entry : params.entrySet()) {
                paramsValue = paramsValue.append(String.format("&%s=%s", entry.getKey(), URLEncoder.encode(entry.getValue(), "utf-8")));
            }
            finalUrl = String.format("%s?%s", url, paramsValue.substring(1));
        }
        return doGet(finalUrl, headers);
    }

    private static String doGet(String finalUrl, Map<String, String> headers) throws IOException {
        OkHttpClient httpClient = getInstance();
        Request.Builder buider = new Request.Builder();
        if (headers != null) {
            for (Map.Entry<String, String> entry : headers.entrySet()) {
                buider.addHeader(entry.getKey(), entry.getValue());
            }
        }
        Request request = buider.url(finalUrl).build();
        log.info("httpGet >>> {}", finalUrl);
        Response response = httpClient.newCall(request).execute();
        if (response == null || response.body() == null) {
            log.info("httpGet >>> {} >>> no response", finalUrl);
            return null;
        } else {
            String bodyString = response.body().string();
            log.info("httpGet >>> {}    ,responseBody>>>{}", finalUrl, bodyString);
            return bodyString;
        }
    }

    private static Map<String, Object> bodyStringFormat(String bodyString) {
        Map<String, Object> tmp = JacksonUtils.json2map(bodyString);
        tmp.put("response_json", bodyString);
        return tmp;
    }

    @AllArgsConstructor
    @Getter
    public enum MediaEnum {
        JSON("application/json;charset=utf-8"),
        FORM(""),
        XML("text/xml"),
        ;
        private String contentType;
    }

}
